package org.itydl.game

import javafx.scene.input.KeyCode
import javafx.scene.input.KeyEvent
import org.itheima.kotlin.game.core.Window
import org.itydl.game.business.*
import org.itydl.game.enums.Direction
import org.itydl.game.model.*
import java.io.File
import java.util.concurrent.CopyOnWriteArrayList

/**
 * 坦克大战窗体,继承自Window即可
 */
class GameWindow : Window(title = "坦克大战1.0", icon = "img/star.gif", width = Config.gameWidth, height = Config.gameHeight) {

    /**
     * 所有元素的集合管理容器，便于创建和绘制
     */
    private val mViews = CopyOnWriteArrayList<View>()
    /**
     * 我方坦克
     * lateinit 修饰符，延迟初始化，表示修饰的变量晚点使用的时候创建
     */
    private lateinit var mTank: Tank

    /**
     * 游戏是否结束(大本营销毁时，游戏就是结束状态)
     */
    private var gameOver: Boolean = false

    /**
     * 敌军坦克出生地位置集合
     */
    private val enemyBornLocation = CopyOnWriteArrayList<Pair<Int, Int>>()

    /**
     * 被激活的坦克最多数量
     */
    private var activeEnemySize = 6

    /**
     * 总共的坦克数量
     */
    private var totalEnemySize = 10

    /**
     * 出生位置索引
     */
    private var bornIndex = 0

    /**
     * 窗体创建
     */
    override fun onCreate() {
//        println("onCreate()")
        // 创建地图中的元素
        // 地图的绘制,读取配置文件，创建元素(绘制对象)，并添加到集合
        // file：1.map文件对象
        val file = File(javaClass.getResource("/map/1.map").path)
        // lines：1.map文件里面的所有行信息封装对象,是一个集合，集合的每一个元素为1.map里面的一行
        val lines: List<String> = file.readLines()
        var lineNum = 0
        // 遍历行；lineObj代表每一行的对象封装
        lines.forEach { lineObj ->
            var columnNum = 0
            // 遍历列，columnChar代表每一行中的当前列的字符。如：砖、水、铁、草...
            lineObj.toCharArray().forEach { columnChar ->
                when (columnChar) {
                    // 根据行号、列号计算每个元素的位置
                    '砖' -> mViews.add(Wall(columnNum * Config.block, lineNum * Config.block))
                    '水' -> mViews.add(Water(columnNum * Config.block, lineNum * Config.block))
                    '草' -> mViews.add(Grass(columnNum * Config.block, lineNum * Config.block))
                    '铁' -> mViews.add(Iron(columnNum * Config.block, lineNum * Config.block))
                    '敌' -> enemyBornLocation.add(Pair(columnNum * Config.block, lineNum * Config.block))
                }
                // 列号依次增加
                columnNum++
            }
            // 行号依次增加
            lineNum++
        }

        // 创建的时候同时创建坦克
        mTank = Tank(Config.block * 10, Config.block * 12)
        mViews.add(mTank)

        mViews.add(Camp(Config.gameWidth / 2 - Config.block, Config.gameHeight - Config.block - Config.block / 2))
    }

    /**
     * 窗体显示渲染，（窗体的显示是不停地绘制自己，肉眼无法看清）该方法会不停地回调执行
     * 在这里进行绘制操作
     */
    override fun onDisplay() {
//        println("onDisplay()，size：" + mViews.size)
        // 遍历所有元素进行绘制
        mViews.forEach {
            it.draw()
        }
    }

    /**
     * 按键响应，例如按下enter键，可以在这里获取按下该按键的事件
     * 只要按下按键，调用onKeyPressed()后，会调用onDisplay()方法，然后才会调用onRefresh方法
     */
    override fun onKeyPressed(event: KeyEvent) {
//        println("onKeyPressed()" + event.code)
        if (gameOver) {
            return
        }
        when (event.code) {
            KeyCode.W -> {
                mTank.move(Direction.UP)
            }
            KeyCode.S -> {
                mTank.move(Direction.DOWN)
            }
            KeyCode.A -> {
                mTank.move(Direction.LEFT)
            }
            KeyCode.D -> {
                mTank.move(Direction.RIGHT)
            }
            KeyCode.ENTER -> {
                // 坦克具备发射(创建子弹对象)的行为；创建一个子弹,子弹由坦克发出，返回值就是一个子弹
                val bullet: Bullet = mTank.shot()
                mViews.add(bullet)
            }
        }
    }

    /**
     * 窗体耗时操作逻辑,跟 onDisplay() 一样都是不断的绘制自己，会一直被调用
     * 刷新、业务逻辑。该方法一般会跟 onCreate() 方法联系紧密
     */
    override fun onRefresh() {

        // 过滤器过滤出所有具备销毁能力的物体，如果到了销毁时机，则销毁(移除views管理器)
        mViews.filter { it is Destoryable }.forEach {
            val isDestory = (it as Destoryable).isDestory()
            if (isDestory) {

                if (it is Enemy) {
                    // 如果是敌人，就减少敌军数量
                    totalEnemySize--
                }

                // 具备销毁条件，移除View
                mViews.remove(it)
                val destory = it.showDestory()
                destory?.let {
                    mViews.addAll(destory)
                }
            }
        }

        if (gameOver) {
            // 游戏结束不再刷新后续逻辑
            return
        }
//        println("onRefresh()")
        var badDirection: Direction? = null
        var badBlock: Blockable? = null
        // 业务操作
        // 碰撞检测
        // 过滤出所有运动的物体集合 it is Movable判断it是否是Movable类型
        mViews.filter { it is Movable }.forEach { move ->
            // move当前可以移动的物体
            // move as Movable把move转为Movable类型
            move as Movable
            mViews.filter { (it is Blockable) and (it != move) }.forEach blockTag@{ block ->
                // and (it != move)不跟自己碰撞校验
                // block当前具备阻挡能力的物体
                // 移动物体和阻挡能力的物体检测碰撞
                block as Blockable
                // 获取碰撞方向，这里可以画图理解
                // 碰撞的方向，可空，如果direction=null表示没有发生碰撞（一直往该方向运动）如果不为null说明发生了碰撞
                // 为什么要交给move移动体去判断？因为要根据坐标计算，移动体里面才有坐标，是否发生碰撞需要把固定块放进去
                val direction: Direction? = move.willCollication(block)
                direction?.let {
                    // direction不为null时候才走这里，找到发生碰撞的block快。跳出第一层循环，后面的块就不要去参与检测了
                    badDirection = direction
                    badBlock = block
                    return@blockTag
                }
            }
            // 找到和move碰撞的阻塞快，找到会碰撞的方向。通知移动的物体 会在哪个物体哪个方向发生碰撞
            // 找到了通知移动物体
            move.notifyCollication(badDirection, badBlock)
        }

        // 过滤器过滤出所有可移动的物体
        mViews.filter { it is AutoMovable }.forEach { move ->
            move as AutoMovable
            move.autoMove()
        }

        // 过滤器过滤出所有具备攻击能力的物体(例如子弹)
        mViews.filter { it is Attackable }.forEach { attach ->
            attach as Attackable
            // 过滤器过滤出所有遭受攻击能力的物体(例如坦克)  (it != attach.from)具备攻击能力不能攻击自己！
            // (it != attach)具备攻击能力的不能攻击自己（自己的子弹不能攻击自己，攻击别人的子弹可以）
            mViews.filter { (it is Sufferable) and (it != attach.from) and (it != attach) }.forEach sufferTag@{ suffer ->
                suffer as Sufferable
                // 是否发生碰撞
                val collection = attach.isCollection(suffer)
                if (collection) {
                    // 找到这个被攻击的物体，不再继续找
                    // 通知攻击者，找到攻击者
                    attach.notifyAttached(suffer)
                    // 通知被攻击者，被谁攻击
                    val suffereds: Array<View>? = suffer.notifySuffered(attach)
                    suffereds?.forEach {
                        mViews.add(it)
                    }
                    return@sufferTag
                }
            }
        }

        // 过滤自动射击的物体
        mViews.filter { it is AutoShot }.forEach {
            it as AutoShot
            val shot: View? = it.autoShot()
            shot?.let {
                // 只添加不为空的子弹
                mViews.add(shot)
            }
        }

        // 判断游戏是否结束。大本营消失不在集合管理中时表示销毁状态
        if (mViews.filter { it is Camp }.isEmpty() || totalEnemySize <= 0) {
            gameOver = true
        }

        // 添加地方坦克，检测敌方出生
        // 数量少于
        if ((totalEnemySize > 0) and (totalEnemySize >= activeEnemySize) and (mViews.filter { it is Enemy }.size < activeEnemySize)) {
            // 对多少个位置取余，按照位置，轮流显示坦克
            bornIndex %= enemyBornLocation.size
            val pair = enemyBornLocation[bornIndex]
            mViews.add(Enemy(pair.first, pair.second))

            bornIndex++
        }
    }
}